/// @file Configuration.cpp
/// @brief static class, read the xml config file and initialises all the
/// the objects and managers used in the flocking system

#include <iostream>
#include "QString"

#include "pugixml.hpp"

#include "boost/tokenizer.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/foreach.hpp>

#include "ngl/Colour.h"

#include "Configuration.h"


//static variables initialisation
std::string Configuration::s_settingsDoc = "config/Settings.xml";
bool Configuration::s_debug = false;

std::vector<float> Configuration::getFloatVectorFromString
                                            (
                                                const std::string _str,
                                                const int _d
                                            )
{
    //split string into array of strings by ","
    std::vector<std::string> strArray;
    boost::split(strArray, _str, boost::is_any_of(","));

    //loop through strings and convert to float array
    std::vector<float> floatArray;
    for (int i = 0; i < _d; i++) floatArray.push_back(boost::lexical_cast<float>(strArray[i]));

    //return array of float
    return floatArray;
}

void Configuration::initialiseWindow
                                (
                                    int &o_frameRate,
                                    int &o_mouseMoveSensitivity
                                )
{
    //load settings xml file
    pugi::xml_document doc;
    doc.load_file(s_settingsDoc.c_str());

    //load global settings here
    pugi::xpath_node_set node = doc.select_nodes("/Settings/Global");

    //read fps params
    o_frameRate = node.first().node().attribute("fpsFrequency").as_int();

    //read fps params
    o_mouseMoveSensitivity = node.first().node().attribute("mouseSensitivity").as_int();

}

ngl::Camera Configuration::initialiseCamera(const float _aspect)
{
    //load settings xml file
    pugi::xml_document doc;
    doc.load_file(s_settingsDoc.c_str());

    //get camera node
    pugi::xpath_node_set node = doc.select_nodes("/Settings/Camera");

    //get near and far plane values
    float near = node.first().node().attribute("near").as_float();
    float far = node.first().node().attribute("far").as_float();

    //get fov
    int fov = node.first().node().attribute("fov").as_int();

    //get from and to values and convert to float arrays
    std::vector<float> from = getFloatVectorFromString(node.first().node().attribute("from").value(), 3);
    std::vector<float> to = getFloatVectorFromString(node.first().node().attribute("to").value(), 3);

    //build vectors
    ngl::Vector eye(from[0], from[1], from[2]);
    ngl::Vector look(to[0], to[1], to[2]);
    ngl::Vector up(0, 1, 0);

    //create a camera with the read data
    ngl::Camera cam;
    cam.set(eye, look, up);
    cam.setShape(fov, _aspect, near, far, ngl::PERSPECTIVE);

    return cam;
}

ngl::Light Configuration::initialiseLight()
{
    //load settings xml file
    pugi::xml_document doc;
    doc.load_file(s_settingsDoc.c_str());

    //get camera node
    pugi::xpath_node_set node = doc.select_nodes("/Settings/Light");

    //get to values and convert to float array
    std::vector<float> to = getFloatVectorFromString(node.first().node().attribute("to").value(), 3);

    //get color values and convert to float array
    std::vector<float> color = getFloatVectorFromString(node.first().node().attribute("color").value(), 4);

    //build vectors
    ngl::Vector direction(to[0], to[1], to[2]);
    ngl::Colour lightColor(color[0], color[1], color[2], color[3]);

    //create a light with the read data
    ngl::Light light(direction, lightColor, ngl::LIGHTREMOTE);

    return light;
}

std::vector<ShaderObject*>* Configuration::initialiseShader()
{
    //load settings xml file
    pugi::xml_document doc;
    doc.load_file(s_settingsDoc.c_str());

    std::vector<ShaderObject*>* result = new std::vector<ShaderObject*>;

    //get list of normal shaders
    pugi::xpath_node_set node = doc.select_nodes("/Settings/Shaders/Shader");
    for (pugi::xpath_node_set::const_iterator it = node.begin(); it != node.end(); ++it)
    {
        //get details for a shader
        pugi::xpath_node node = *it;

        //save values in array
        std::string name = node.node().attribute("name").value();
        std::string vs = node.node().attribute("vertex").value();
        std::string fs = node.node().attribute("fragment").value();

        //create a shader and add it to our list
        result->push_back(new ShaderObject(name, vs, fs));
    }

    return result;
}

void Configuration::initialiseFlock
                                (
                                    int &o_initialNumberOfBoids,
                                    bool &o_normaliseRules,
                                    bool &o_cohesionEnabled,
                                    bool &o_separationEnabled,
                                    bool &o_alignmentEnabled,
                                    float &o_cohesionWeight,
                                    float &o_separationWeight,
                                    float &o_alignmentWeight,
                                    float &o_separationThreshold,
                                    float &o_neighbouringThreshold,
                                    bool &o_enableFOVNeighbouring,
                                    float &o_neighbouringFOVAngle,
                                    std::string &o_boidModel,
                                    ngl::Vector &o_tendTowardPosition,
                                    float &o_tendTowardWeight,
                                    ngl::Vector &o_externalForce,
                                    float &o_externalForceWeight,
                                    bool &o_enableTendToward,
                                    bool &o_enableExternalForce,
                                    bool &o_enableObstacleAvoidance,
                                    float &o_obstacleAvoidanceWeight,
                                    float &o_minSpeed,
                                    float &o_maxSpeed
                                )
{
    //load settings xml file
    pugi::xml_document doc;
    doc.load_file(s_settingsDoc.c_str());

    //load global settings here
    pugi::xpath_node_set node = doc.select_nodes("/Settings/Flock");

    //read xml tags, convert to their appropriate formats and save into the reference variables
    o_initialNumberOfBoids = boost::lexical_cast<int>(node.first().node().select_single_node("InitialNumberOfBoids").node().child_value());

    o_normaliseRules = boost::lexical_cast<bool>(node.first().node().select_single_node("NormaliseRules").node().child_value());

    o_cohesionEnabled = node.first().node().select_single_node("Cohesion").node().attribute("enabled").as_bool();
    o_separationEnabled = node.first().node().select_single_node("Separation").node().attribute("enabled").as_bool();
    o_alignmentEnabled = node.first().node().select_single_node("Alignment").node().attribute("enabled").as_bool();

    o_cohesionWeight = node.first().node().select_single_node("Cohesion").node().attribute("weight").as_float();
    o_separationWeight = node.first().node().select_single_node("Separation").node().attribute("weight").as_float();
    o_alignmentWeight = node.first().node().select_single_node("Alignment").node().attribute("weight").as_float();

    o_separationThreshold = boost::lexical_cast<float>(node.first().node().select_single_node("SeparationThreshold").node().child_value());

    o_neighbouringThreshold = node.first().node().select_single_node("Neighbouring").node().attribute("distance").as_float();
    o_enableFOVNeighbouring = node.first().node().select_single_node("Neighbouring").node().attribute("enableFOV").as_bool();
    o_neighbouringFOVAngle = node.first().node().select_single_node("Neighbouring").node().attribute("fovAngle").as_float();

    o_boidModel = node.first().node().select_single_node("Model").node().attribute("name").value();

    std::vector<float> pos = getFloatVectorFromString(node.first().node().select_single_node("TendToward").node().attribute("position").value(), 3);
    o_tendTowardPosition.set(pos[0], pos[1], pos[2]);
    o_tendTowardWeight = node.first().node().select_single_node("TendToward").node().attribute("weight").as_float();
    o_enableTendToward = node.first().node().select_single_node("TendToward").node().attribute("enabled").as_bool();

    std::vector<float> force = getFloatVectorFromString(node.first().node().select_single_node("ExternalForce").node().attribute("force").value(), 3);
    o_externalForce.set(force[0], force[1], force[2]);
    o_externalForceWeight = node.first().node().select_single_node("ExternalForce").node().attribute("weight").as_float();
    o_enableExternalForce = node.first().node().select_single_node("ExternalForce").node().attribute("enabled").as_bool();

    o_enableObstacleAvoidance = node.first().node().select_single_node("ObstacleAvoidance").node().attribute("enabled").as_bool();
    o_obstacleAvoidanceWeight = node.first().node().select_single_node("ObstacleAvoidance").node().attribute("weight").as_float();

    o_minSpeed = node.first().node().select_single_node("Speed").node().attribute("min").as_float();
    o_maxSpeed = node.first().node().select_single_node("Speed").node().attribute("max").as_float();


}

std::vector<StaticObject*>* Configuration::initialiseObstacles()
{
    //load settings xml file
    pugi::xml_document doc;
    doc.load_file(s_settingsDoc.c_str());

    std::vector<StaticObject*>* result = new std::vector<StaticObject*>;

    //get list of obstacles
    pugi::xpath_node_set node = doc.select_nodes("/Settings/Obstacles/Obstacle");
    for (pugi::xpath_node_set::const_iterator it = node.begin(); it != node.end(); ++it)
    {
        //get details for an obstacle
        pugi::xpath_node node = *it;

        //get position values and convert to float array
        std::vector<float> pos = getFloatVectorFromString(node.node().attribute("position").value(), 3);
        ngl::Vector position(pos[0], pos[1], pos[2]);

        //get radii
        float br = node.node().attribute("boundingRadius").as_float();
        float ir = node.node().attribute("influenceSphere").as_float();

        //create an obstacle and add it to our list
        result->push_back(new StaticObject(position, br, ir));
    }

    return result;

}
